package com.atguigu.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.srb.base.exception.Assert;
import com.atguigu.srb.base.result.ResponseEnum;
import com.atguigu.srb.core.enums.BorrowInfoStatusEnum;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.enums.ReturnMethodEnum;
import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.pojo.entity.*;
import com.atguigu.srb.core.mapper.LendMapper;
import com.atguigu.srb.core.pojo.vo.BorrowInfoApprovalVO;
import com.atguigu.srb.core.pojo.vo.BorrowerDetailVO;
import com.atguigu.srb.core.service.*;
import com.atguigu.srb.core.utils.*;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author Atguigu
 * @since 2023-01-04
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {
    @Resource
    DictService dictService;
    @Resource
    BorrowerService borrowerService;
    @Resource
    UserAccountService userAccountService;
    @Resource
    LendItemService lendItemService;
    @Resource
    UserInfoService userInfoService;
    @Resource
    TransFlowService transFlowService;
    @Resource
    LendReturnService lendReturnService;
    @Resource
    LendItemReturnService  lendItemReturnService;
    @Override
    public void createLend(BorrowInfo borrowInfo, BorrowInfoApprovalVO borrowInfoApprovalVO) {
        Lend lend = new Lend();
        lend.setUserId(borrowInfo.getUserId());
        lend.setBorrowInfoId(borrowInfo.getId());
        lend.setLendNo(LendNoUtils.getLendNo());//额外的标的的唯一编号
        lend.setTitle(borrowInfoApprovalVO.getTitle());
        lend.setAmount(borrowInfo.getAmount());
        lend.setPeriod(borrowInfo.getPeriod());
        lend.setLendYearRate(borrowInfoApprovalVO.getLendYearRate()
                .divide(new BigDecimal("100"),8,BigDecimal.ROUND_DOWN));
        lend.setServiceRate(borrowInfoApprovalVO.getServiceRate()
                .divide(new BigDecimal("100"),8,BigDecimal.ROUND_DOWN));
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        lend.setLowestAmount(new BigDecimal(100));
        lend.setInvestNum(0);
        lend.setLendStartDate(borrowInfoApprovalVO.getLendStartDate());
        //lend.setLendEndDate(borrowInfoApprovalVO.getLendStartDate()+borrowInfo.getPeriod());
        //borrowInfoApprovalVO.getLendStartDate()+borrowInfo.getPeriod()
        //标的结束时间： 起息日+投资期数
        lend.setLendEndDate(new DateTime(borrowInfoApprovalVO.getLendStartDate())
            .plusMonths(borrowInfo.getPeriod())
            .toDate());
        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());
        //预期收益： 投资期数*月化率*标的金额
        //月化率= 年化率/12
        BigDecimal lendMonthRate = borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(1200),
                8, BigDecimal.ROUND_DOWN);
        BigDecimal expectAmount = lendMonthRate.multiply(borrowInfo.getAmount())
                .multiply(new BigDecimal(borrowInfo.getPeriod()));
        lend.setExpectAmount(expectAmount);
        lend.setInvestAmount(new BigDecimal(0));
        //初始化标的时 状态设置为募资中
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());
        lend.setPublishDate(new Date());
        this.save(lend);
    }

    @Override
    public List<Lend> listLends() {
        List<Lend> lends = this.list();
        lends.forEach(lend->{
            updateLendDetail(lend);
        });

        return lends;
    }

    private void updateLendDetail(Lend lend) {
        String status = null;
        switch(lend.getStatus()){
            case 0:
                status = LendStatusEnum.CHECK.getMsg();
                break;
            case 1:
                status = LendStatusEnum.INVEST_RUN.getMsg();
                break;
            case 2:
                status = LendStatusEnum.PAY_RUN.getMsg();
                break;
            case 3:
                status = LendStatusEnum.PAY_OK.getMsg();
                break;
            case 4:
                status = LendStatusEnum.FINISH.getMsg();
                break;
            default:
                status = LendStatusEnum.CANCEL.getMsg();
                break;
        }
        lend.getParam().put("status",status);
        Dict dict = dictService.getDictByDictCodeAndValue("returnMethod", lend.getReturnMethod());
        lend.getParam().put("returnMethod",dict.getName());
    }

    @Override
    public Map<String, Object> getLendDetail(String id) {
        //1、标的信息
        Lend lend = this.getById(id);
        updateLendDetail(lend);
        //2、借款人信息
        //获取借款人id
        Borrower borrower = borrowerService.getOne(Wrappers.lambdaQuery(Borrower.class)
                .eq(Borrower::getUserId, lend.getUserId()));
        BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailById(borrower.getId().toString());

        Map<String, Object> map = new HashMap<>();
        map.put("lend",lend);
        map.put("borrowerDetailVO",borrowerDetailVO);
        return map;
    }

     @Override
    public BigDecimal getInterestCount(BigDecimal investAmount, String id) {
        //查询标的信息
        Lend lend = this.getById(id);
        Integer period = lend.getPeriod();
        Integer returnMethod = lend.getReturnMethod();
        BigDecimal lendYearRate = lend.getLendYearRate();
        BigDecimal interestCount;
        if (returnMethod == ReturnMethodEnum.ONE.getMethod()){
            interestCount = Amount1Helper.getInterestCount(investAmount, lendYearRate, period);
        }else if (returnMethod == ReturnMethodEnum.TWO.getMethod()){
            interestCount = Amount2Helper.getInterestCount(investAmount, lendYearRate, period);
        }else if (returnMethod == ReturnMethodEnum.THREE.getMethod()){
            interestCount = Amount3Helper.getInterestCount(investAmount, lendYearRate, period);
        }else{
            interestCount = Amount4Helper.getInterestCount(investAmount, lendYearRate, period);
        }
        return interestCount;
    }

    //放款
    @Override
    public void makeLoan(String id) {
        Lend lend = this.getById(id);
        Assert.assertTrue(lend.getStatus()!=LendStatusEnum.INVEST_RUN.getStatus(),
                ResponseEnum.ERROR);
        Map<String, Object> map = new HashMap<>();
        map.put("agentId",HfbConst.AGENT_ID);
        map.put("agentProjectCode", lend.getLendNo());
        map.put("agentBillNo",LendNoUtils.getLoanNo());//本次放款编号
        // 募资总金额*月化率*期数
        BigDecimal lendMonthRate = lend.getServiceRate().divide(new BigDecimal("12"), 8, BigDecimal.ROUND_DOWN);
        BigDecimal mchFee = lend.getInvestAmount().multiply(new BigDecimal(lend.getPeriod()))
                .multiply(lendMonthRate);
        map.put("mchFee",mchFee);//手续费
        map.put("timestamp",System.currentTimeMillis());
        map.put("sign",RequestHelper.getSign(map));
        //hfb 放款接口 需要同步访问，不需要回调接口
        //调用hfb的放款接口时：hfb会自动扣除放款标的的 投资用户的冻结金额 并转入到借款用户的账户中
        JSONObject jsonObject = RequestHelper.sendRequest(map, HfbConst.MAKE_LOAD_URL);
        String resultCode = jsonObject.get("resultCode").toString();
        log.error("放款失败：{}",jsonObject.toJSONString());
        Assert.assertTrue(!"0000".equals(resultCode) , ResponseEnum.ERROR);
        //1、本地借款人账户入账：
        //BigDecimal voteAmt = new BigDecimal(jsonObject.get("voteAmt").toString());
        BigDecimal borrowAmt = lend.getInvestAmount().subtract(mchFee);
        Long userId = lend.getUserId();
        UserAccount userAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class)
                .eq(UserAccount::getUserId, userId));
        userAccount.setAmount(userAccount.getAmount().add(borrowAmt));
        userAccountService.updateById(userAccount);
        //=== 保存交易日志
        UserInfo userInfo = userInfoService.getById(userId);
        TransFlow transFlow = new TransFlow();
        transFlow.setUserId(userId);
        transFlow.setUserName(userInfo.getName());
        transFlow.setTransNo(LendNoUtils.getTransNo());
        transFlow.setTransType(TransTypeEnum.BORROW_BACK.getTransType());
        transFlow.setTransTypeName(TransTypeEnum.BORROW_BACK.getTransTypeName());
        transFlow.setTransAmount(userAccount.getAmount());
        transFlow.setMemo(TransTypeEnum.BORROW_BACK.getTransTypeName()+": "+borrowAmt+" 元");
        transFlowService.save(transFlow);
        //2、本地投资人账户冻结投资金额扣除：
        //一个标的有多个投资人，一个投资人可以投资多个标的(投资人冻结金额可能投资的是多个标的)
        //查询本标的所有已支付的投资记录，遍历从投资人账户冻结金额中扣除投资记录金额
        //2.1 查询本标的 成功的投资记录列表
        List<LendItem> lendItems = lendItemService.list(Wrappers.lambdaQuery(LendItem.class)
                .eq(LendItem::getLendId, lend.getId())
                .eq(LendItem::getStatus, 1));

        lendItems.forEach(lendItem -> {
            //一条投资记录
            BigDecimal investAmt = lendItem.getInvestAmount();
            Long investUserId = lendItem.getInvestUserId();
            //查询投资人账户
            UserAccount investAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class)
                    .eq(UserAccount::getUserId, investUserId));
            investAccount.setFreezeAmount(investAccount.getFreezeAmount().subtract(investAmt));
            userAccountService.updateById(investAccount);
            // === 保存交易日志：
            UserInfo investUserInfo = userInfoService.getById(investUserId);
            TransFlow investTransFlow = new TransFlow();
            investTransFlow.setUserId(investUserId);
            investTransFlow.setUserName(investUserInfo.getName());
            investTransFlow.setTransNo(LendNoUtils.getTransNo());
            investTransFlow.setTransType(TransTypeEnum.INVEST_UNLOCK.getTransType());
            investTransFlow.setTransTypeName(TransTypeEnum.INVEST_UNLOCK.getTransTypeName());
            investTransFlow.setTransAmount(investAccount.getAmount());
            investTransFlow.setMemo(TransTypeEnum.INVEST_UNLOCK.getTransTypeName()+": "+investAmt.doubleValue()+" 元");
            transFlowService.save(investTransFlow);
        });
        //3、修改标的的状态为还款中
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        this.updateById(lend);
        //4、生成还款计划：lend_return
        //按照 标的借款的期数 生成若干期 还款计划 ，一个还款计划就是一个LendReturn
        //还款起始日期从 lend.getLendStartDate()开始
        List<LendReturn> lendReturns = new ArrayList<>();
        for (Integer i = 1; i <= lend.getPeriod(); i++) {
            LendReturn lendReturn = new LendReturn();
            lendReturn.setLendId(lend.getId());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());//标的总金额
            lendReturn.setBaseAmount(lend.getInvestAmount());//标的募资金额
            lendReturn.setCurrentPeriod(i);//当前还款期数
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setReturnMethod(lend.getReturnMethod());
            //投资人较多： 每个投资人会生成 一个 还款计划对应的一个回款计划
            //生成回款计划后：累加每一期所有投资人回款计划的以下三个值 设置给还款计划
//            lendReturn.setPrincipal();//本期还款本金
//            lendReturn.setInterest();//本期还款的利息
//            lendReturn.setTotal();//本期还款总金额
            lendReturn.setFee(new BigDecimal("0"));
            if(i==1){
                lendReturn.setReturnDate(lend.getLendStartDate());
            }else{
                lendReturn.setReturnDate(new DateTime(lend.getLendStartDate())
                    .plusMonths(i-1).toDate());
            }
            lendReturn.setOverdue(false);
            lendReturn.setLast(i==lend.getPeriod());
            lendReturn.setStatus(0);//未归还

            lendReturns.add(lendReturn);
        }
        //批量保存还款计划
        lendReturnService.saveBatch(lendReturns);
        //因为后续要使用 期数 获取lendReturn对象的id，可以将lendReturns集合中的每个 期数id的映射转为map
        Map<Integer, Long> periodReturnIdMap = lendReturns.stream().collect(Collectors.toMap(LendReturn::getCurrentPeriod,
                LendReturn::getId));

        //5、生成回款计划：lend_item_return
        //5.1 查询标的所有的成功的投资记录:使用上面查询到的
        //5.2 遍历投资记录
        //每个投资记录生成 标的的期数个 回款计划
        List<LendItemReturn> allLendItemReturns = new ArrayList<>();

        lendItems.forEach(lendItem -> {//遍历投资记录
            //因为后面需要计算每个回款计划的本金利息，计算方式都一样，可以通过工具类 获取月份对应的 还款本金利息
            Map<Integer, BigDecimal> perMonthPrincipal;
            Map<Integer, BigDecimal> perMonthInterest;
            if(lend.getReturnMethod() == ReturnMethodEnum.ONE.getMethod()){
                perMonthPrincipal = Amount1Helper.getPerMonthPrincipal(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount1Helper.getPerMonthInterest(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
            }else if(lend.getReturnMethod() == ReturnMethodEnum.TWO.getMethod()){
                perMonthPrincipal = Amount2Helper.getPerMonthPrincipal(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount2Helper.getPerMonthInterest(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
            }else if(lend.getReturnMethod() == ReturnMethodEnum.THREE.getMethod()){
                perMonthPrincipal = Amount3Helper.getPerMonthPrincipal(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount3Helper.getPerMonthInterest(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
            }else{
                perMonthPrincipal = Amount4Helper.getPerMonthPrincipal(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount4Helper.getPerMonthInterest(lendItem.getInvestAmount(),
                        lendItem.getLendYearRate(), lend.getPeriod());
            }
            for (Integer i = 0; i < lend.getPeriod(); i++) {
                LendItemReturn lendItemReturn = new LendItemReturn();//创建回款计划对象
                lendItemReturn.setLendReturnId(periodReturnIdMap.get(i+1));//对应的还款计划id： 应该和当前期数对应的还款计划的id
                lendItemReturn.setLendItemId(lendItem.getId());
                lendItemReturn.setLendId(lendItem.getLendId());
                lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
                lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
                lendItemReturn.setCurrentPeriod(i+1);
                lendItemReturn.setLendYearRate(lendItem.getLendYearRate());
                lendItemReturn.setReturnMethod(lend.getReturnMethod());
                //如果还款方式为4 一次性还本还息  只有最后一个月需要还  其他月份设置为0
                if(lend.getReturnMethod()==ReturnMethodEnum.FOUR.getMethod()){
                    if((i+1)==lend.getPeriod()){
                        //最后一期
                        lendItemReturn.setPrincipal(perMonthPrincipal.get(1));//本金
                        lendItemReturn.setInterest(perMonthInterest.get(1));//利息
                        lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest())); //本金+利息
                    }else{
                        lendItemReturn.setPrincipal(new BigDecimal("0"));//本金
                        lendItemReturn.setInterest(new BigDecimal("0"));//利息
                        lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest())); //本金
                    }
                }else{
                    lendItemReturn.setPrincipal(perMonthPrincipal.get(i+1));//本金
                    lendItemReturn.setInterest(perMonthInterest.get(i+1));//利息
                    lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest())); //本金+利息
                }
                lendItemReturn.setFee(new BigDecimal(0));
                lendItemReturn.setReturnDate(new DateTime(lend.getLendStartDate())
                    .plusMonths(i).toDate());
                lendItemReturn.setOverdue(false);
                lendItemReturn.setOverdueTotal(new BigDecimal(0));
                lendItemReturn.setStatus(0);

                allLendItemReturns.add(lendItemReturn);
            }
        });
        //保存回款计划到数据库中
        lendItemReturnService.saveBatch(allLendItemReturns);
        //计算每个还款计划的 本金利息 总金额

        lendReturns.forEach(lendReturn -> {
            //通过正在遍历的还款计划对象的 期数 获取 回款计划集合中相同期数的 回款计划集合
            BigDecimal totalPrincipal = allLendItemReturns.stream()
                    //挑选指定期的  回款计划集合
                    .filter(lendItemReturn -> lendItemReturn.getCurrentPeriod() == lendReturn.getCurrentPeriod())
                    // 将挑选的 所有的回款计划对象的 本金字段挑选出来
                    .map(LendItemReturn::getPrincipal)
                    //遍历上一步挑选的本金字段数据集合  每两个相加
                    .reduce((a, b) -> a.add(b))
                    //获取相加的结果
                    .get();
            BigDecimal totalInterest = allLendItemReturns.stream()
                    //挑选指定期的  回款计划集合
                    .filter(lendItemReturn -> lendItemReturn.getCurrentPeriod() == lendReturn.getCurrentPeriod())
                    // 将挑选的 所有的回款计划对象的 本金字段挑选出来
                    .map(LendItemReturn::getInterest)
                    //遍历上一步挑选的本金字段数据集合  每两个相加
                    .reduce((a, b) -> a.add(b))
                    //获取相加的结果
                    .get();
            BigDecimal totalSum = allLendItemReturns.stream()
                    //挑选指定期的  回款计划集合
                    .filter(lendItemReturn -> lendItemReturn.getCurrentPeriod() == lendReturn.getCurrentPeriod())
                    // 将挑选的 所有的回款计划对象的 本金字段挑选出来
                    .map(LendItemReturn::getTotal)
                    //遍历上一步挑选的本金字段数据集合  每两个相加
                    .reduce((a, b) -> a.add(b))
                    //获取相加的结果
                    .get();
            lendReturn.setPrincipal(totalPrincipal);
            lendReturn.setInterest(totalInterest);
            lendReturn.setTotal(totalSum);
        });
        lendReturnService.updateBatchById(lendReturns);
    }
}
